﻿using System;
using System.Collections;
using System.Drawing;
using System.Windows.Forms;
using DevExpress.Utils.Drawing;
using DevExpress.XtraEditors.Drawing;
using DevExpress.XtraEditors.Repository;
using DevExpress.XtraEditors.ViewInfo;
using DevExpress.XtraGrid.Columns;
using DevExpress.XtraGrid.Views.Base;
using DevExpress.XtraGrid.Views.Grid;
using DevExpress.XtraGrid.Views.Grid.ViewInfo;

namespace ContractManager.Common.Service
{
    /// <summary>
    /// Grid check marks selection. 
    /// </summary>
    /// <remarks>
    /// Golovkov Alex, 07.10.2010. 
    /// </remarks>
    public class GridCheckMarksSelection
    {
        #region Constants and Fields

        /// <summary>
        /// The selection.
        /// </summary>
        protected ArrayList Selection;

        /// <summary>
        /// The view.
        /// </summary>
        protected GridView view;

        /// <summary>
        /// The column.
        /// </summary>
        private GridColumn column;

        /// <summary>
        /// The edit.
        /// </summary>
        private RepositoryItemCheckEdit edit;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="GridCheckMarksSelection"/> class.    Default constructor. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        public GridCheckMarksSelection()
        {
            this.Selection = new ArrayList();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GridCheckMarksSelection"/> class. 
        /// Default constructor. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        /// <param name="view">
        /// The view. 
        /// </param>
        public GridCheckMarksSelection(GridView view)
            : this()
        {
            this.View = view;
        }

        #endregion

        #region Properties

        /// <summary>   Gets the check mark column. </summary>
        /// <value> The check mark column. </value>
        public GridColumn CheckMarkColumn
        {
            get
            {
                return this.column;
            }
        }

        /// <summary>   Gets the number of selecteds. </summary>
        /// <value> The number of selecteds. </value>
        public int SelectedCount
        {
            get
            {
                return this.Selection.Count;
            }
        }

        /// <summary>   Gets or sets the view. </summary>
        /// <value> The view. </value>
        public GridView View
        {
            get
            {
                return this.view;
            }

            set
            {
                if (this.view == value)
                {
                    return;
                }

                this.Detach();
                this.Attach(value);
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Clears the selection. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        public void ClearSelection()
        {
            this.Selection.Clear();
            this.Invalidate();
        }

        /// <summary>
        /// Gets a selected index. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        /// <param name="row">
        /// The row. 
        /// </param>
        /// <returns>
        /// The selected index. 
        /// </returns>
        public int GetSelectedIndex(object row)
        {
            return this.Selection.IndexOf(row);
        }

        /// <summary>
        /// Gets a selected row. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        /// <param name="index">
        /// Zero-based index of the. 
        /// </param>
        /// <returns>
        /// The selected row. 
        /// </returns>
        public object GetSelectedRow(int index)
        {
            return this.Selection[index];
        }

        /// <summary>
        /// Query if 'rowHandle' is group row selected. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        /// <param name="rowHandle">
        /// Handle of the row. 
        /// </param>
        /// <returns>
        /// true if group row selected, false if not. 
        /// </returns>
        public bool IsGroupRowSelected(int rowHandle)
        {
            for (int i = 0; i < this.view.GetChildRowCount(rowHandle); i++)
            {
                int row = this.view.GetChildRowHandle(rowHandle, i);
                if (this.view.IsGroupRow(row))
                {
                    if (!this.IsGroupRowSelected(row))
                    {
                        return false;
                    }
                }
                else if (!this.IsRowSelected(row))
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Query if 'rowHandle' is row selected. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        /// <param name="rowHandle">
        /// Handle of the row. 
        /// </param>
        /// <returns>
        /// true if row selected, false if not. 
        /// </returns>
        public bool IsRowSelected(int rowHandle)
        {
            if (this.view.IsGroupRow(rowHandle))
            {
                return this.IsGroupRowSelected(rowHandle);
            }

            object row = this.view.GetRow(rowHandle);
            return this.GetSelectedIndex(row) != -1;
        }

        /// <summary>
        /// Select all. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        public void SelectAll()
        {
            this.Selection.Clear();
            if (this.view.DataSource is ICollection)
            {
                this.Selection.AddRange((ICollection)this.view.DataSource); // fast
            }
            else
            {
                for (int i = 0; i < this.view.DataRowCount; i++)
                {
                    // slow
                    this.Selection.Add(this.view.GetRow(i));
                }
            }

            this.Invalidate();
        }

        /// <summary>
        /// Select group. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        /// <param name="rowHandle">
        /// Handle of the row. 
        /// </param>
        /// <param name="select">
        /// true to select, false to deselect. 
        /// </param>
        public void SelectGroup(int rowHandle, bool select)
        {
            if (this.IsGroupRowSelected(rowHandle) && select)
            {
                return;
            }

            for (int i = 0; i < this.view.GetChildRowCount(rowHandle); i++)
            {
                int childRowHandle = this.view.GetChildRowHandle(rowHandle, i);
                if (this.view.IsGroupRow(childRowHandle))
                {
                    this.SelectGroup(childRowHandle, select);
                }
                else
                {
                    this.SelectRow(childRowHandle, select, false);
                }
            }

            this.Invalidate();
        }

        /// <summary>
        /// Select row. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        /// <param name="rowHandle">
        /// Handle of the row. 
        /// </param>
        /// <param name="select">
        /// true to select, false to deselect. 
        /// </param>
        public void SelectRow(int rowHandle, bool select)
        {
            this.SelectRow(rowHandle, select, true);
        }

        #endregion

        #region Methods

        /// <summary>
        /// Attaches. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        /// <param name="gv">
        /// The gv. 
        /// </param>
        protected virtual void Attach(GridView gv)
        {
            if (gv == null)
            {
                return;
            }

            this.Selection.Clear();
            this.view = gv;
            this.edit = gv.GridControl.RepositoryItems.Add("CheckEdit") as RepositoryItemCheckEdit;
            if (this.edit != null)
            {
                this.edit.EditValueChanged += this.EditEditValueChanged;
            }

            this.column = gv.Columns.Add();
            this.column.OptionsColumn.AllowSort = DevExpress.Utils.DefaultBoolean.False;
            this.column.VisibleIndex = int.MaxValue;
            this.column.FieldName = "CheckMarkSelection";
            this.column.Caption = @"Mark";
            this.column.OptionsColumn.ShowCaption = false;
            this.column.UnboundType = DevExpress.Data.UnboundColumnType.Boolean;
            this.column.ColumnEdit = this.edit;

            gv.Click += this.ViewClick;
            gv.CustomDrawColumnHeader += this.ViewCustomDrawColumnHeader;
            gv.CustomDrawGroupRow += this.ViewCustomDrawGroupRow;
            gv.CustomUnboundColumnData += this.ViewCustomUnboundColumnData;
            gv.RowStyle += this.ViewRowStyle;
            gv.MouseDown += this.ViewMouseDown; // clear Selection
        }

        /// <summary>
        /// Detaches this object. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        protected virtual void Detach()
        {
            if (this.view == null)
            {
                return;
            }

            if (this.column != null)
            {
                this.column.Dispose();
            }

            if (this.edit != null)
            {
                this.view.GridControl.RepositoryItems.Remove(this.edit);
                this.edit.Dispose();
            }

            this.view.Click -= this.ViewClick;
            this.view.CustomDrawColumnHeader -= this.ViewCustomDrawColumnHeader;
            this.view.CustomDrawGroupRow -= this.ViewCustomDrawGroupRow;
            this.view.CustomUnboundColumnData -= this.ViewCustomUnboundColumnData;
            this.view.RowStyle -= this.ViewRowStyle;
            this.view.MouseDown -= this.ViewMouseDown;

            this.view = null;
        }

        /// <summary>
        /// Draw check box. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        /// <param name="g">
        /// The. 
        /// </param>
        /// <param name="r">
        /// The. 
        /// </param>
        /// <param name="checked">
        /// The checked.
        /// </param>
        protected void DrawCheckBox(Graphics g, Rectangle r, bool @checked)
        {
            var info = this.edit.CreateViewInfo() as CheckEditViewInfo;
            var painter = this.edit.CreatePainter() as CheckEditPainter;
            if (info == null)
            {
                return;
            }

            info.EditValue = @checked;
            info.Bounds = r;
            info.CalcViewInfo(g);
            var args = new ControlGraphicsInfoArgs(info, new GraphicsCache(g), r);

            if (painter != null)
            {
                painter.Draw(args);
            }

            args.Cache.Dispose();
        }

        /// <summary>
        /// Edit value changed. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        /// <param name="sender">
        /// Source of the event. 
        /// </param>
        /// <param name="e">
        /// Event information. 
        /// </param>
        private void EditEditValueChanged(object sender, EventArgs e)
        {
            this.view.PostEditor();
        }

        /// <summary>
        /// Invalidates this object. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        private void Invalidate()
        {
            this.view.BeginUpdate();
            this.view.EndUpdate();
        }

        /// <summary>
        /// Select row. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        /// <param name="rowHandle">
        /// Handle of the row. 
        /// </param>
        /// <param name="select">
        /// true to select, false to deselect. 
        /// </param>
        /// <param name="invalidate">
        /// true to invalidate. 
        /// </param>
        private void SelectRow(int rowHandle, bool select, bool invalidate)
        {
            if (this.IsRowSelected(rowHandle) == select)
            {
                return;
            }

            object row = this.view.GetRow(rowHandle);
            if (select)
            {
                this.Selection.Add(row);
            }
            else
            {
                this.Selection.Remove(row);
            }

            if (invalidate)
            {
                this.Invalidate();
            }
        }

        /// <summary>
        /// View click. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        /// <param name="sender">
        /// Source of the event. 
        /// </param>
        /// <param name="e">
        /// Event information. 
        /// </param>
        private void ViewClick(object sender, EventArgs e)
        {
            Point pt = this.view.GridControl.PointToClient(Control.MousePosition);
            GridHitInfo info = this.view.CalcHitInfo(pt);
            if (info.InColumn && info.Column == this.column)
            {
                if (this.SelectedCount == this.view.DataRowCount)
                {
                    this.ClearSelection();
                }
                else
                {
                    this.SelectAll();
                }
            }

            if (info.InRow && this.view.IsGroupRow(info.RowHandle) && info.HitTest != GridHitTest.RowGroupButton)
            {
                bool selected = this.IsGroupRowSelected(info.RowHandle);
                this.SelectGroup(info.RowHandle, !selected);
            }
        }

        /// <summary>
        /// View custom draw column header. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        /// <param name="sender">
        /// Source of the event. 
        /// </param>
        /// <param name="e">
        /// Event information. 
        /// </param>
        private void ViewCustomDrawColumnHeader(object sender, ColumnHeaderCustomDrawEventArgs e)
        {
            if (e.Column != this.column)
            {
                return;
            }

            e.Info.InnerElements.Clear();
            e.Painter.DrawObject(e.Info);
            this.DrawCheckBox(e.Graphics, e.Bounds, this.SelectedCount == this.view.DataRowCount);
            e.Handled = true;
        }

        /// <summary>
        /// View custom draw group row. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        /// <param name="sender">
        /// Source of the event. 
        /// </param>
        /// <param name="e">
        /// Event information. 
        /// </param>
        private void ViewCustomDrawGroupRow(object sender, RowObjectCustomDrawEventArgs e)
        {
            var info = e.Info as GridGroupRowInfo;

            if (info == null)
            {
                return;
            }

            info.GroupText = "         " + info.GroupText.TrimStart();
            e.Info.Paint.FillRectangle(e.Graphics, e.Appearance.GetBackBrush(e.Cache), e.Bounds);
            e.Painter.DrawObject(e.Info);

            Rectangle r = info.ButtonBounds;
            r.Offset(r.Width * 2, 0);
            this.DrawCheckBox(e.Graphics, r, this.IsGroupRowSelected(e.RowHandle));
            e.Handled = true;
        }

        /// <summary>
        /// View custom unbound column data. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        /// <param name="sender">
        /// Source of the event. 
        /// </param>
        /// <param name="e">
        /// Event information. 
        /// </param>
        private void ViewCustomUnboundColumnData(object sender, CustomColumnDataEventArgs e)
        {
            if (e.Column != this.CheckMarkColumn)
            {
                return;
            }

            if (e.IsGetData)
            {
                e.Value = this.IsRowSelected(e.ListSourceRowIndex);
            }
            else
            {
                this.SelectRow(e.ListSourceRowIndex, (bool)e.Value);
            }
        }

        /// <summary>
        /// View mouse down. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        /// <param name="sender">
        /// Source of the event. 
        /// </param>
        /// <param name="e">
        /// Event information. 
        /// </param>
        private void ViewMouseDown(object sender, MouseEventArgs e)
        {
            if (e.Clicks == 1 && e.Button == MouseButtons.Left)
            {
                Point pt = this.view.GridControl.PointToClient(Control.MousePosition);
                GridHitInfo info = this.view.CalcHitInfo(pt);
                if (info.InRow && info.Column != this.column && this.view.IsDataRow(info.RowHandle))
                {
                    this.ClearSelection();
                    this.SelectRow(info.RowHandle, true);
                }
            }
        }

        /// <summary>
        /// View row style. 
        /// </summary>
        /// <remarks>
        /// Golovkov Alex, 07.10.2010. 
        /// </remarks>
        /// <param name="sender">
        /// Source of the event. 
        /// </param>
        /// <param name="e">
        /// Event information. 
        /// </param>
        private void ViewRowStyle(object sender, RowStyleEventArgs e)
        {
            if (!this.IsRowSelected(e.RowHandle))
            {
                return;
            }

            e.Appearance.BackColor = SystemColors.Highlight;
            e.Appearance.ForeColor = SystemColors.ActiveCaption;
        }

        #endregion
    }
}
